#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <scsi/scsi_device.h>
#include <linux/blk-mq.h>

MODULE_AUTHOR("Arkis");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test-page");


static const char *g_devpath = "/dev/sdc";
static struct block_device *g_blkdev = NULL;
static struct bdev_handle *g_bdev_handle = NULL;
static struct request_queue *g_req_queue = NULL;
static struct page *g_page = NULL;

enum io_type {
    IOTYPE_READ = 0,
    IOTYPE_WRITE,
};

static int get_blk_device(void)
{
    g_bdev_handle = bdev_open_by_path(g_devpath, BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL);
    if (g_bdev_handle == NULL) {
        printk(KERN_ERR "Lookup bdev failed.\n");
		return -1;
    }

    g_blkdev = g_bdev_handle->bdev;
	if (IS_ERR(g_blkdev)) {
        printk(KERN_ERR "Get device failed [%s]\n", g_devpath);
        g_blkdev = NULL;
        bdev_release(g_bdev_handle);
		return -1;
	}

    g_req_queue = (struct request_queue *)g_blkdev->bd_queue;
    if (g_req_queue == NULL) {
        printk(KERN_ERR "Get request queue failed [%s]\n", g_devpath);
        bdev_release(g_bdev_handle);
        return -1;
    }
    printk(KERN_INFO "Get device success\n");
    return 0;
}

struct page *create_data_page(void)
{
    char *data = NULL;
    struct page *data_page = NULL;

    data_page = alloc_page(GFP_KERNEL);
    if (data_page == NULL) {
        printk(KERN_ERR "alloc_page failed\n");
        return NULL;
    }
    data = kmap(data_page);
    memset(data, 0, PAGE_SIZE);
    strncpy(data, "Hello, World!", strlen("Hello, World!"));
    kunmap(data_page);
    g_page = data_page;
    return data_page;
}

struct bio *create_bio(struct page *page, enum io_type iotype)
{
    int ret = 0;
    struct bio *bio;
    blk_opf_t opf = 0;

    if (iotype == IOTYPE_READ) {
        opf = REQ_OP_READ;	
    } else if (iotype == IOTYPE_WRITE){
        opf = REQ_OP_WRITE;
    } else {
        printk(KERN_ERR "Unsupported io type {%d}\n", iotype);
        return NULL;
    }
    
    // 创建bio结构
    bio = bio_alloc(g_blkdev, 1, opf, GFP_KERNEL);
    if (bio == NULL) {
        printk(KERN_ERR "Failed to allocate bio.\n");
        return NULL;
    }
    
    // 将page添加到bio
    ret = bio_add_page(bio, page, PAGE_SIZE, 0);
    if (ret != PAGE_SIZE) {
        printk(KERN_ERR "Failed to add page to bio.\n");
        bio_put(bio);
        return NULL;
    }
    bio->bi_iter.bi_sector = 0;  // 设置bio的起始扇区
    bio_set_dev(bio, g_blkdev);  // 设置目标设备
    return bio;
}

enum rq_end_io_ret test_rq_end_io_fn(struct request *req, blk_status_t status)
{
    struct bio *bio = req->bio;
    printk(KERN_INFO "rq_end_io_fn\n");
    if (bio) {
        bio_put(bio);
    }
    __free_page(g_page);
    return RQ_END_IO_FREE;
}

struct request *create_request(struct bio *bio)
{
    struct request *req = NULL;

    req = blk_mq_alloc_request(g_req_queue, bio->bi_opf, 0);
    if (req == NULL) {
        printk(KERN_ERR "Failed to allocate request.\n");
        bio_put(bio);
        return NULL;
    }
    blk_rq_append_bio(req, bio);
    req->__sector = 0;
    req->end_io = test_rq_end_io_fn;
    return req;
}

struct request *setup_request(void)
{
    struct page *data_page = NULL;
    struct bio *bio = NULL;
    struct request *req = NULL;

    data_page = create_data_page();
    if (data_page == NULL) {
        return NULL;
    }

    bio = create_bio(data_page, IOTYPE_WRITE);
    if (bio == NULL) {
        __free_page(data_page);
        return NULL;
    }

    req = create_request(bio);
    if (req == NULL) {
        __free_page(data_page);
        bio_put(bio);
        return NULL;
    }
    return req;
}

void startio(void)
{
    int ret = -1;
    struct request *req = NULL;

    ret = get_blk_device();
    if (ret) {
        printk(KERN_ERR "Failed to get block device\n");
        return;
    }

    req = setup_request();
    if (req == NULL) {
        printk(KERN_ERR "Failed to setup request\n");
        return;
    }

    if (blk_queue_dying(g_req_queue)) {
        printk(KERN_ERR "Request queue is dying\n");
        return;
    }
    ret = blk_insert_cloned_request(req);
    printk(KERN_INFO "submit request ret %d\n", ret);
}

static int __init test_init(void)
{
    printk("test_start\n");
    startio();
    printk("test_done\n");
    return 0;
}

static void __exit test_exit(void)
{
    printk("test_exit\n");
    if (g_bdev_handle != NULL) {
        bdev_release(g_bdev_handle);
    }
}

module_init(test_init)
module_exit(test_exit)
